//////////////////////////////////////////////////////////////////////
// Vec3.h
//
// SHEN Fangyang
// me@shenfy.com
//
// Copyright (C) SHEN Fangyang, 2011, All rights reserved.
//////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
//  Brief Description
//
//	SIMPLE LINEAR ALGEBRA PACKAGE
//
//	3D vector class with basic linear algebra support
//
//////////////////////////////////////////////////////////////////////
#pragma once
#include <ostream>

namespace grflib
{
	namespace LA
	{
		template<typename T>
		struct CVec3
		{
			union{
				struct
				{
					T _x, _y, _z;
				};
				T vec[3];
			};

			CVec3(void);
			CVec3(T x, T y, T z);
			CVec3(const CVec3 &v);


			void SetValue(T value);
			void SetValue(T x, T y, T z);

			inline static T DotProduct(const CVec3 &a, const CVec3 &b);
			inline static CVec3<T> &CrossProduct(CVec3 &out, const CVec3 &a, const CVec3 &b);
			
			friend inline T DotProduct(const CVec3 &a, const CVec3 &b);
			friend inline const CVec3<T> CrossProduct(const CVec3 &a, const CVec3 &b);

			inline void Normalize(void);

			//generate tanget vectors given Y direction (surface normal usually)
			static void TangentVectors(_out_ CVec3 &x, _inout_ CVec3 &y, _out_ CVec3 &z);

			//convert a VECTOR from coordinate system A to B given B's (axes) and (origin) in A
			CVec3 &CoordTransform(_in_ const CVec3 &coord, _in_ CVec3 &N);
			static CVec3 &CoordTransform(_out_ CVec3 &coordOut, _in_ const CVec3 &coordIn, _in_ CVec3 &N);
			static CVec3 &CoordTransform(_out_ CVec3 &coordOut, _in_ const CVec3 &coordIn, 
									//global axis must be normalized first!
									_in_ const CVec3 &axisX, 
									_in_ const CVec3 &axisY, 
									_in_ const CVec3 &axisZ);

			//unary operatros
			CVec3<T> &operator += (const CVec3<T> &rhs);
			CVec3<T> &operator -= (const CVec3<T> &rhs);
			CVec3<T> &operator *= (const T &rhs);
			T dot (const CVec3<T> &rhs);
            
            template<typename TT>
            CVec3<T> &operator= (const CVec3<TT> &rhs);

			//binary operators
			template<typename TT>
			friend inline CVec3<TT> operator+ (const CVec3<TT>& lhs, const CVec3<TT>& rhs);
			template<typename TT>
			friend inline CVec3<TT> operator+ (const CVec3<TT>& rhs);
			template<typename TT>
			friend inline CVec3<TT> operator- (const CVec3<TT>& lhs, const CVec3<TT>& rhs);
			template<typename TT>
			friend inline CVec3<TT> operator- (const CVec3<TT>& rhs);
			template<typename TT>
			friend inline CVec3<TT> operator* (const CVec3<TT>& lhs, const TT& rhs);
			template<typename TT>
			friend inline CVec3<TT> operator* (const TT& lhs, const CVec3<TT>& rhs);

			//debug text dump
			template<typename TT>
			friend std::ostream & operator<< (std::ostream & out, const CVec3<TT>& vec);
		};

		template<typename T>
		CVec3<T>::CVec3(void) : 
			_x(static_cast<T>(0.0f)), _y(static_cast<T>(0.0f)), _z(static_cast<T>(0.0f))
		{}

		template<typename T>
		CVec3<T>::CVec3(T x, T y, T z) :
			_x(x), _y(y), _z(z)
		{}

		template<typename T>
		CVec3<T>::CVec3(const CVec3 &v)
		{	_x = v._x;	_y = v._y;	_z = v._z;	}

        template<typename T> template <typename TT>
        CVec3<T> &CVec3<T>::operator= (const CVec3<TT> &rhs)
        {
            _x = static_cast<T>(rhs._x);
            _y = static_cast<T>(rhs._y);
            _z = static_cast<T>(rhs._z);
        }

		template<typename T>
		void CVec3<T>::SetValue(T value)
		{	_x = value;	_y = value;	_z = value;	}

		template<typename T>
		void CVec3<T>::SetValue(T x, T y, T z)
		{	_x = x;	_y = y;	_z = z;	}

		template<typename T>
		T CVec3<T>::dot(const CVec3<T> &rhs)
		{
			return _x * rhs._x + _y * rhs._y + _z * rhs._z;
		}
		
		template<typename T>
		inline  T CVec3<T>::DotProduct(const CVec3 &a, const CVec3 &b)
		{
			return a._x * b._x + a._y * b._y + a._z * b._z;
		}

		template<typename T>
		inline T DotProduct(const CVec3<T> &a, const CVec3<T> &b)
		{
			return a._x * b._x + a._y * b._y + a._z * b._z;
		}

		template<typename T>
		inline CVec3<T>& CVec3<T>::CrossProduct(CVec3 &out, const CVec3 &a, const CVec3 &b)
		{
			out._x = a._y * b._z - a._z * b._y;
			out._y = a._z * b._x - a._x * b._z;
			out._z = a._x * b._y - a._y * b._x;
			return out;
		}

		template<typename T>
		inline const CVec3<T> CrossProduct(const CVec3<T> &a, const CVec3<T> &b)
		{
			return CVec3<T>(a._y * b._z - a._z * b._y,
						 a._z * b._x - a._x * b._z,
						 a._x * b._y - a._y * b._x);
		}

		template<typename T>
		inline CVec3<T> &CVec3<T>::operator += (const CVec3<T> &rhs)
		{
			_x += rhs._x;
			_y += rhs._y;
			_z += rhs._z;
			return (*this);
		}

		template<typename T>
		inline CVec3<T> &CVec3<T>::operator -= (const CVec3<T> &rhs)
		{
			_x -= rhs._x;
			_y -= rhs._y;
			_z -= rhs._z;
			return (*this);
		}

		template<typename T>
		inline CVec3<T> &CVec3<T>::operator *= (const T &rhs)
		{
			_x *= rhs;
			_y *= rhs;
			_z *= rhs;
			return (*this);
		}

#define CVEC3_BINARY_OPERATORS(op) \
		template<typename T> \
		inline CVec3<T> operator op (const CVec3<T>& lhs, const CVec3<T>& rhs) \
		{ \
			CVec3<T> res(lhs); \
			res op##= rhs; \
			return (res); \
		}

		CVEC3_BINARY_OPERATORS(+)
		CVEC3_BINARY_OPERATORS(-)

#undef CVEC3_BINARY_OPERATORS

		template<typename T>
		inline CVec3<T> operator* (const CVec3<T>& lhs, const T& rhs)
		{
			CVec3<T> res(lhs);
			res *= rhs;
			return res;
		}

		template<typename T>
		inline CVec3<T> operator* (const T& lhs, const CVec3<T>& rhs)
		{
			CVec3<T> res(rhs);
			res *= lhs;
			return res;
		}

		template<typename T>
		inline CVec3<T> operator- (const CVec3<T>& rhs)
		{
			return (CVec3<T>(-rhs._x, -rhs._y, -rhs._z));
		}

		template<typename T>
		inline CVec3<T> operator+ (const CVec3<T>& rhs)
		{
			return rhs;
		}

		template<typename T>
		void CVec3<T>::TangentVectors(CVec3 &x, _inout_ CVec3 &y, CVec3 &z)
		{
			CVec3<T> tmpVec;
			T absx, absy, absz;
			absx = abs(y._x);
			absy = abs(y._y);
			absz = abs(y._z);

			if (absx <= absy && absx <= absz)
			{
				tmpVec.SetValue(1.0f, 0.0f, 0.0f);

			}
			else if (absy <= absx && absy <= absz)
			{
				tmpVec.SetValue(0.0f, 1.0f, 0.0f);
			}
			else
			{
				tmpVec.SetValue(0.0f, 0.0f, 1.0f);
			}

			CVec3<T>::CrossProduct(x, tmpVec, y);
            if (abs(x._x) >= abs(x._z))
            {
                CVec3<T>::CrossProduct(z, x, y);
            }
            else
            {
                z = x;
                CVec3<T>::CrossProduct(x, y, z);
            }
			x.Normalize();
			y.Normalize();
			z.Normalize();
		}

		template<typename T>
		CVec3<T> & CVec3<T>::CoordTransform(CVec3<T> &newCoord, const CVec3<T> &coord, CVec3<T> &N)
		{
			CVec3<T> Nx, Nz;
			CVec3<T> vx, vy, vz;
			//generate tangent vectors
			TangentVectors(Nx, N, Nz);

			CoordTransform(newCoord, coord, Nx, N, Nz);
            return newCoord;
		}

		template<typename T>
		CVec3<T> &CVec3<T>::CoordTransform(CVec3<T> &newCoord, 
                                   const CVec3<T> &coord,
								   const CVec3 &axisX,
								   const CVec3 &axisY,
								   const CVec3 &axisZ)
		{
			newCoord._x = DotProduct(axisX, coord);
			newCoord._y = DotProduct(axisY, coord);
			newCoord._z = DotProduct(axisZ, coord);
			newCoord.Normalize();
            return newCoord;
		}


		template<typename T>
		CVec3<T> &CVec3<T>::CoordTransform(const CVec3<T> &coord, CVec3<T> &N)
		{
			CoordTransform((*this), coord, N);
            return *this;
		}

		template<typename T>
		std::ostream & operator<< (std::ostream &out, const CVec3<T> &vec)
		{
			out << vec._x << " " << vec._y << " " << vec._z << std::endl;
			return out;
		}

		template<typename T>
		inline void CVec3<T>::Normalize()
		{
			T norm = sqrt(_x * _x + _y * _y + _z * _z);
			_x /= norm;
			_y /= norm;
			_z /= norm;
		}
	}
}

typedef grflib::LA::CVec3<float> float3;